package com.zmn.mcc.business.impl.staff;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.api.config.annotation.NacosValue;
import com.aliyun.openservices.shade.com.alibaba.rocketmq.common.message.MessageClientIDSetter;
import com.dingtalk.api.response.OapiProcessinstanceGetResponse;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.zmn.base.common.data.common.dio.company.CompanyDIO;
import com.zmn.base.common.data.common.dro.company.CompanyDRO;
import com.zmn.base.common.data.dubbo.interfaces.company.CompanyListRemoteService;
import com.zmn.base.engineer.common.constants.DubboConsts;
import com.zmn.base.engineer.common.dro.foreign.engineer.ForeignEngineerBasicInfoDRO;
import com.zmn.base.engineer.common.dro.supervisor.SupervisorDetailDRO;
import com.zmn.base.engineer.dubbo.interfaces.engineer.basic.EngineerBasicInfoModifyRemoteService;
import com.zmn.base.engineer.dubbo.interfaces.foreign.EngineerListRemoteService;
import com.zmn.base.engineer.dubbo.interfaces.supervisor.EngineerSupervisorListRemoteService;
import com.zmn.base.engineer.dubbo.interfaces.supervisor.EngineerSupervisorModifyRemoteService;
import com.zmn.base.plat.engine.common.constant.CompanyConsts;
import com.zmn.base.plat.engine.common.constant.PlatEngineConsts;
import com.zmn.common.constant.GlobalConsts;
import com.zmn.common.constant.StatusConsts;
import com.zmn.common.dictionary.GlobalDict;
import com.zmn.common.dto.LoginStaff;
import com.zmn.common.dto2.AMISListData;
import com.zmn.common.dto2.ResponseDTO;
import com.zmn.common.utils.collection.CollectionUtil;
import com.zmn.common.utils.date.DateUtil;
import com.zmn.common.utils.number.NumberUtil;
import com.zmn.common.utils.number.RandomUtil;
import com.zmn.common.utils.office.ExcelUtil;
import com.zmn.common.utils.office.excel.ExportExcel;
import com.zmn.common.utils.pinyin.PinYinUtil;
import com.zmn.common.utils.string.StringUtil;
import com.zmn.manager.common.database.annotation.ReadOnlyConnection;
import com.zmn.manager.mq.interfaces.ZmnMQSender;
import com.zmn.mcc.business.interfaces.permit.PermitBService;
import com.zmn.mcc.business.interfaces.staff.StaffBService;
import com.zmn.mcc.business.interfaces.staff.ip.StaffIpInfoBService;
import com.zmn.mcc.cache.*;
import com.zmn.mcc.common.constant.DpermitConsts;
import com.zmn.mcc.common.constant.StaffChangeLogConsts;
import com.zmn.mcc.common.constant.StaffConsts;
import com.zmn.mcc.common.constant.SystemConsts;
import com.zmn.mcc.common.dictionary.StaffDict;
import com.zmn.mcc.common.dto.permit.DpermitLitDO;
import com.zmn.mcc.common.dto.permit.PermitLitDO;
import com.zmn.mcc.common.dto.permit.SpermitDTO;
import com.zmn.mcc.common.dto.staff.*;
import com.zmn.mcc.common.enums.DpermitEnum;
import com.zmn.mcc.manager.config.MccConfig;
import com.zmn.mcc.model.entity.dept.DeptIdAndRoleId;
import com.zmn.mcc.model.entity.dept.McDept;
import com.zmn.mcc.model.entity.log.IpInfo;
import com.zmn.mcc.model.entity.organize.McOrganize;
import com.zmn.mcc.model.entity.role.McRole;
import com.zmn.mcc.model.entity.role.McRoleStaff;
import com.zmn.mcc.model.entity.role.RoleIdAndDayLimit;
import com.zmn.mcc.model.entity.staff.McStaff;
import com.zmn.mcc.model.entity.staff.McStaffDpermitQuery;
import com.zmn.mcc.model.entity.staff.McStaffQuery;
import com.zmn.mcc.model.entity.staff.StaffIdAndDeptId;
import com.zmn.mcc.model.entity.staff.dingtalk.StaffDingTalk;
import com.zmn.mcc.model.entity.staff.staffchangelog.StaffChangeLog;
import com.zmn.mcc.model.entity.staff.staffperformance.StaffPerformance;
import com.zmn.mcc.model.mq.SendParams;
import com.zmn.mcc.model.mq.SmsParams;
import com.zmn.mcc.model.vo.McStaffExportExcelVO;
import com.zmn.mcc.model.vo.McStaffVO;
import com.zmn.mcc.services.impl.staff.PasswordService;
import com.zmn.mcc.services.interfaces.dept.DeptDpermitService;
import com.zmn.mcc.services.interfaces.dept.DeptService;
import com.zmn.mcc.services.interfaces.organize.OrganizeService;
import com.zmn.mcc.services.interfaces.role.RoleService;
import com.zmn.mcc.services.interfaces.role.RoleStaffService;
import com.zmn.mcc.services.interfaces.staff.StaffDpermitService;
import com.zmn.mcc.services.interfaces.staff.StaffService;
import com.zmn.mcc.services.interfaces.staff.staffarchives.StaffArchivesService;
import com.zmn.mcc.services.interfaces.staff.staffchangelog.StaffChangeLogService;
import com.zmn.mcc.services.interfaces.staff.staffcontract.StaffContractService;
import com.zmn.mcc.services.interfaces.staff.stafffamily.StaffFamilyService;
import com.zmn.mcc.services.interfaces.staff.staffperformance.StaffPerformanceService;
import com.zmn.tapi.common.dingtalk.ProcessInstanceTopDRO;
import com.zmn.tapi.dubbo.interfaces.dingtalk.DingTalkAuditService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import javax.validation.constraints.NotNull;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author heguanghua
 * @since 2020/04/14 22:22
 */
@Service
@Slf4j
public class StaffBServiceImpl implements StaffBService {
    protected Logger logger = LoggerFactory.getLogger(StaffBServiceImpl.class);

    private static final String TAG = "员工管理业务接口实现类";

    private static final String PASSWORD_PREFIX = "zmn";
    private static final String MQ_TAG = "message_center_system";

    /**
     * 审批结果通过
     */
    private static final String AUDIT_PASS = "agree";

    @DubboReference(version = DubboConsts.INTERFACE_VERSION, check = false)
    private EngineerListRemoteService engineerListRemoteService;
    @DubboReference(version = DubboConsts.INTERFACE_VERSION, check = false)
    private EngineerBasicInfoModifyRemoteService engineerBasicInfoModifyRemoteService;
    @DubboReference(version = DubboConsts.INTERFACE_VERSION, check = false)
    private EngineerSupervisorModifyRemoteService engineerSupervisorModifyRemoteService;

    @NacosValue(value = "${spring.mq.topic.push}", autoRefreshed = true)
    private String pushTopic;
    @NacosValue(value = "${spring.sendmessage.ruleid}", autoRefreshed = true)
    private Integer ruleId;

    // region Resource
    @Resource
    private StaffService staffService;
    @Resource
    private StaffPerformanceService staffPerformanceService;
    @Resource
    private OrganizeService organizeService;
    @DubboReference(version = com.zmn.base.plat.engine.common.constant.DubboConsts.INTERFACE_VERSION, check = false)
    private CompanyListRemoteService companyListRemoteService;
    @DubboReference(version = DubboConsts.INTERFACE_VERSION, check = false)
    private EngineerSupervisorListRemoteService engineerSupervisorListRemoteService;
    @Resource
    private CompanyCache companyCache;
    @Resource
    private DeptService deptService;
    @Resource
    private PermitBService permitBService;
    @Resource
    private RoleStaffService roleStaffService;
    @Resource
    private StaffDpermitService staffDpermitService;
    @Resource
    private DeptDpermitService deptDpermitService;
    @Resource
    private StaffCache staffCache;
    @Resource
    private RoleCache roleCache;
    @Resource
    private OrganizeCache organizeCache;
    @Resource
    private DeptCache deptCache;
    @Resource
    private AreaCache areaCache;
    @Resource
    private ChannelCache channelCache;
    @Resource
    private CubeCache cubeCache;
    @Resource
    private ProductGroupCache productGroupCache;
    @Resource
    private MccConfig config;
    @Resource
    StaffIpInfoBService staffIpInfoBService;
    @DubboReference(version = DubboConsts.INTERFACE_VERSION, check = false)
    private DingTalkAuditService dingTalkAuditService;

    @Resource
    RoleService roleService;

    @Resource
    private ZmnMQSender zmnMQSender;

    private PasswordService passwordService = new PasswordService();

    @Resource
    private StaffChangeLogService staffChangeLogService;

    @Resource
    private StaffArchivesService staffArchivesService;

    @Resource
    private StaffContractService staffContractService;

    @Resource
    private StaffFamilyService staffFamilyService;

    @Value("${spring.mq.topic.mcc}")
    public String mccTopic;

    public static final String SYNC_EDIT_STAFF_KEY = "sync_edit_staff_key";
    // endregion


    // region 带有角色与数据权限相关字段的员工对象

    @Override
    @ReadOnlyConnection
    public LoginStaffDO getLoginStaffByStaffId(Integer staffId) {

        if (staffId == null) {
            throw new NullPointerException("null staffId");
        }

        LoginStaffDO loginStaff = staffCache.getLoginStaff(staffId);
        if (loginStaff == null) {
            loginStaff = this.getLoginStaffByMcStaff(staffCache.getStaff(staffId));
            staffCache.setLoginStaff(loginStaff);
        }
        return loginStaff;

//        if (staffId == null) {
//            throw new NullPointerException("null staffId");
//        }
        //return this.getLoginStaffByMcStaff(staffCache.getStaff(staffId));
    }

    @Override
    @ReadOnlyConnection
    public LoginStaffDO getLoginStaffByMcStaff(McStaff staff) {

        if (staff == null) {
            return null;
        }

        LoginStaffDO loginStaff = new LoginStaffDO();
        BeanUtils.copyProperties(staff, loginStaff);

        McDept dept = deptCache.getDept(loginStaff.getDeptId());
        if (StringUtils.isEmpty(loginStaff.getDeptName()) && loginStaff.getDeptId() != null) {
            if (dept != null) {
                loginStaff.setDeptName(dept.getDeptName());
            }
        }

        // 员工角色
        List<Integer> roleIds = roleStaffService.listRoleIdByStaffId(staff.getStaffId());
        if (CollectionUtils.isEmpty(roleIds)) {
            if (dept != null && NumberUtil.isNotNullOrZero(dept.getRoleId())) {
                roleIds.add(dept.getRoleId());
            }
        }
        loginStaff.setRoleIds(roleIds);

        /**
         * 员工所属部门
         * 所属部门的数据权限继承部门ID
         */
        if (dept != null) {
            loginStaff.setDpermitDeptId(dept.getDpermitDeptId());
        }

        // 数据导出上限、查询前溯天数、可用角色ID
        int exportLimit = 0;
        int dayLimit = 0;
        int numberLimit = 0;
        List<Integer> validRoleIds = new ArrayList<>();
        if (loginStaff.getRoleIds() != null) {
            for (int roleId : loginStaff.getRoleIds()) {

                McRole role = roleCache.getRole(roleId);
                if (role.getStatus() != GlobalDict.STATUS_ENABLE) {
                    continue;
                }
                validRoleIds.add(roleId);

                int limit = Optional.ofNullable(role.getExportLimit()).orElse(config.getDataExportLimit());
                if (limit > exportLimit) {
                    exportLimit = limit;
                }
                int limit2 = Optional.ofNullable(role.getDayLimit()).orElse(config.getDataDayLimit());
                if (limit2 > dayLimit) {
                    dayLimit = limit2;
                }
                int limit3 = Optional.ofNullable(role.getNumberLimit()).orElse(config.getDataNumberLimit());
                if (limit3 > numberLimit) {
                    numberLimit = limit3;
                }
            }
        }

        if (exportLimit > config.getDataExportLimit()) {
            exportLimit = config.getDataExportLimit();
        }
        if (dayLimit > config.getDataDayLimit()) {
            dayLimit = config.getDataDayLimit();
        }
        if (numberLimit > config.getDataNumberLimit()) {
            numberLimit = config.getDataNumberLimit();
        }
        loginStaff.setExportLimit(exportLimit);
        loginStaff.setDayLimit(dayLimit);
        loginStaff.setNumberLimit(numberLimit);
        loginStaff.setRoleIds(validRoleIds);

        /**
         * 校验不限IP日期
         */
        boolean isOpenDate = staffCache.getOpenDateExists(loginStaff.getStaffId());
        loginStaff.setOpenDate(isOpenDate ? GlobalDict.YESNO_YES : GlobalDict.YESNO_NO);

        // 缓存登录对象
        staffCache.setLoginStaff(loginStaff);

        return loginStaff;
    }

    // endregion

    // region 取得员工功能权限列表

    @Override
    @ReadOnlyConnection
    public SpermitDTO listPermitByLoginStaff(LoginStaffDO loginStaff, Integer sysId) {

        if (loginStaff == null || NumberUtil.isNullOrZero(loginStaff.getStaffId())) {
            return SpermitDTO.fail(StatusConsts.ERROR_PARAMS, "员工对象参数错误，没有取得权限列表");
        }
        if (CollectionUtil.isNullOrEmpty(loginStaff.getRoleIds())) {
            return SpermitDTO.fail(StatusConsts.ERROR_PERMIT, "员工没有可用角色");
        }

        // 未自定义功能权限且无归属部门、未设置部门默认角色，返回空权限
        if (loginStaff.getPermitSelf().equals(GlobalDict.YESNO_NO) && CollectionUtil.isNullOrEmpty(loginStaff.getRoleIds())) {
            return SpermitDTO.success(SpermitDTO.RANGE_EMPTY, Collections.EMPTY_LIST, "OK");
        }

        // 取得功能权限列表
        List<PermitLitDO> items = permitBService.listPermitLitByRoleIds(loginStaff.getRoleIds(), sysId);
        if (CollectionUtil.isNullOrEmpty(items)) {
            return SpermitDTO.success(SpermitDTO.RANGE_EMPTY, Collections.EMPTY_LIST, "OK");
        } else {
            return SpermitDTO.success(SpermitDTO.RANGE_NORMAL, items, "OK");
        }

    }

    @Override
    public SpermitDTO<PermitLitDO> listPermitBySysId(Integer staffId, Integer sysId) {
        LoginStaffDO loginStaffDO = getLoginStaffByStaffId(staffId);

        boolean isSuperAdmin = isSuperAdmin(loginStaffDO.getStaffId());
        if (isSuperAdmin) {
            return SpermitDTO.success(SpermitDTO.RANGE_ALL, Lists.newArrayList());
        }

        return listPermitByLoginStaff(loginStaffDO, sysId);
    }

    // endregion

    // region 取得员工指定类型的数据权限列表

    @Override
    @ReadOnlyConnection
    public SpermitDTO listDpermitByStaffId(Integer staffId, Integer dataType, Boolean clear) {
        LoginStaffDO loginStaff = this.getLoginStaffByStaffId(staffId);
        return this.listDpermitByLoginStaff(loginStaff, dataType, clear);
    }

    @Override
    @ReadOnlyConnection
    public SpermitDTO listDpermitByLoginStaff(LoginStaffDO loginStaff, Integer dataType, Boolean clear) {

        if (loginStaff == null || NumberUtil.isNullOrZero(loginStaff.getStaffId())) {
            return SpermitDTO.fail(StatusConsts.ERROR_PARAMS, "员工对象参数错误，没有取得权限列表");
        }
        if (clear == null) {
            clear = false;
        }

        // 数据权限类型枚举
        DpermitEnum dpermitEnum = DpermitEnum.getByValue(dataType);

        if (Objects.isNull(dpermitEnum)) {
            return SpermitDTO.fail(StatusConsts.ERROR_PARAMS, "没有对应权限数据");
        }

        // 取得指定基础类型的数据权限列表
        SpermitDTO<DpermitLitDO> response = this.listDpermitByLoginStaff(loginStaff, dpermitEnum.getType());
        if (!response.isSuccess() || response.getRange() == SpermitDTO.RANGE_ALL || response.getRange() == SpermitDTO.RANGE_EMPTY) {
            return response;
        }

        // 数据权限列表
        List<DpermitLitDO> dpermitLits = response.getItems();

        // 用于返回的对象
        SpermitDTO spermit = null;

        // 全部数据权限
        if (dpermitEnum.getLevel() == 0) {
            int minLevel = 0;
            if (!clear) {
                spermit = SpermitDTO.success(SpermitDTO.RANGE_NORMAL, dpermitLits, response.getMessage());
            } else {
                minLevel = minLevel(dpermitLits);
                List<Integer> items = new ArrayList<>();
                for (DpermitLitDO dpermitLit : dpermitLits) {
                    listSubData(dpermitLit.getDataId(), dpermitLit.getSubAll(), dpermitLit.getLevel(), minLevel, dpermitEnum.getType(), clear, items);
                }
                spermit = SpermitDTO.success(SpermitDTO.RANGE_NORMAL, items, response.getMessage());
            }
            spermit.setLevel(minLevel);
        }
        // 指定层级数据权限
        else {
            List<Integer> items = new ArrayList<>();
            for (DpermitLitDO dpermitLit : dpermitLits) {
                listSubData(dpermitLit.getDataId(), dpermitLit.getSubAll(), dpermitLit.getLevel(), dpermitEnum.getLevel(), dpermitEnum.getType(), clear, items);
            }
            if (CollectionUtil.isNullOrEmpty(items)) {
                spermit = SpermitDTO.success(SpermitDTO.RANGE_EMPTY, Collections.EMPTY_LIST, "没有取得指定层级的权限列表");
            } else {
                spermit = SpermitDTO.success(SpermitDTO.RANGE_NORMAL, items, response.getMessage());
            }
            spermit.setLevel(dpermitEnum.getLevel());
        }

        return spermit;
    }


    /**
     * 取得指定基础类型的数据权限列表
     *
     * @param loginStaff
     * @param dataType   基础数据权限类型
     * @return
     */
    private SpermitDTO listDpermitByLoginStaff(LoginStaffDO loginStaff, Integer dataType) {

        if (NumberUtil.isNullOrZero(dataType) || !DpermitConsts.TYPE_ALL.contains(dataType)) {
            return SpermitDTO.fail(StatusConsts.ERROR_PARAMS, "数据权限类型参数错误，没有取得权限列表");
        }

        // 是否自定义数据权限
        boolean dpermitSelf = false;
        switch (dataType) {
            case DpermitConsts.TYPE_ORGANIZE:
                dpermitSelf = loginStaff.getDpermitOrgSelf().equals(GlobalDict.YESNO_YES);
                break;
            case DpermitConsts.TYPE_DEPT:
                dpermitSelf = loginStaff.getDpermitDeptSelf().equals(GlobalDict.YESNO_YES);
                break;
            case DpermitConsts.TYPE_AREA:
                dpermitSelf = loginStaff.getDpermitAreaSelf().equals(GlobalDict.YESNO_YES);
                break;
            case DpermitConsts.TYPE_COOPERATION:
                dpermitSelf = Objects.equals(GlobalDict.YESNO_YES,loginStaff.getDpermitCooperationSelf());
                break;
            case DpermitConsts.TYPE_CUBE:
                dpermitSelf = loginStaff.getDpermitCubeSelf().equals(GlobalDict.YESNO_YES);
                break;
            case DpermitConsts.TYPE_PRODUCT:
                dpermitSelf = loginStaff.getDpermitProductSelf().equals(GlobalDict.YESNO_YES);
                break;
            case DpermitConsts.TYPE_MEASURE:
                dpermitSelf = Integer.valueOf(GlobalDict.YESNO_YES).equals(loginStaff.getDpermitMeasureSelf());
                break;
            case DpermitConsts.TYPE_PROMOTION:
                dpermitSelf = Integer.valueOf(PlatEngineConsts.YES).equals(loginStaff.getDpermitPromotionSelf());
                break;
            case DpermitConsts.TYPE_CHANNEL:
                dpermitSelf = Integer.valueOf(PlatEngineConsts.YES).equals(loginStaff.getDpermitChannelSelf());
                break;
            default:
                break;
        }

        // 数据权限列表
        List<DpermitLitDO> items = new ArrayList<>();

        // 自定义数据权限
        if (dpermitSelf) {
            items = staffDpermitService.listDpermitLitByStaffIdAndDataType(loginStaff.getStaffId(), dataType);
        }
        // 继承部门数据权限
        else {
            // 无归属部门或部门未设置默认继承部门，则返回空权限
            if (NumberUtil.isNullOrZero(loginStaff.getDeptId()) || NumberUtil.isNullOrZero(loginStaff.getDpermitDeptId())) {
                return SpermitDTO.success(SpermitDTO.RANGE_EMPTY, Collections.EMPTY_LIST, "默认数据权限部门未设置，没有取得权限列表");
            }
            // 所属部门的数据权限继承部门ID
            items = deptDpermitService.listDpermitLitByStaffIdAndDataType(loginStaff.getDpermitDeptId(), dataType);
        }

        // 没有设置视为具有全部权限
        if (CollectionUtil.isNullOrEmpty(items)) {
            return SpermitDTO.success(SpermitDTO.RANGE_ALL, Collections.EMPTY_LIST, "OK");
        } else {
            return SpermitDTO.success(SpermitDTO.RANGE_NORMAL, items, "OK");
        }
    }

    /**
     * 在数据权限列表中找到最小层级值
     *
     * @param dpermitLits
     * @return
     */
    private int minLevel(List<DpermitLitDO> dpermitLits) {
        int minLevel = 0;
        for (DpermitLitDO dpermitLit : dpermitLits) {
            if (dpermitLit.getLevel() > minLevel) {
                minLevel = dpermitLit.getLevel();
            }
        }
        return minLevel;
    }

    /**
     * 把数据权限列表按层级分解为二维数组，过滤subAll=1的数据权限
     *
     * @param dpermitLits
     * @return
     */
    private List<List> split(List<DpermitLitDO> dpermitLits) {

        List<List> items = new ArrayList<>();
        List<DpermitLitDO> items1 = new ArrayList<>();
        List<DpermitLitDO> items2 = new ArrayList<>();
        List<DpermitLitDO> items3 = new ArrayList<>();
        List<DpermitLitDO> items4 = new ArrayList<>();

        for (DpermitLitDO dpermitLit : dpermitLits) {
            if (dpermitLit.getSubAll() != GlobalDict.YESNO_YES) {
                continue;
            }
            switch (dpermitLit.getLevel()) {
                case GlobalConsts.LEVEL_1:
                    items1.add(dpermitLit);
                    break;
                case GlobalConsts.LEVEL_2:
                    items2.add(dpermitLit);
                    break;
                case GlobalConsts.LEVEL_3:
                    items3.add(dpermitLit);
                    break;
                case GlobalConsts.LEVEL_4:
                    items4.add(dpermitLit);
                    break;
                default:
                    break;
            }
        }
        items.add(items1);
        items.add(items2);
        items.add(items3);
        items.add(items4);

        return items;
    }

    /**
     * 往下迭代找到对应层级的数据ID集合
     * 起始层级为第2层
     *
     * @param dataId       当前权限数据ID
     * @param subAll       是否包括全部下级
     * @param level        当前层级
     * @param minLevel     最低层级
     * @param baseDataType 基础数据权限类型
     * @param clear        是否整理数据
     * @param items        返回的数据集
     */
    private void listSubData(int dataId, int subAll, int level, int minLevel, int baseDataType, boolean clear, List<Integer> items) {
        if (level == minLevel) {
            if (!clear || (clear && subAll == GlobalDict.YESNO_YES)) {
                items.add(dataId);
            }
            return;
        }
        int subLevel = level + 1;
        if (subAll == GlobalDict.YESNO_YES && level < minLevel) {
            List<Integer> subDataIds = listSubDataId(dataId, subLevel, baseDataType);
            for (int subDataId : subDataIds) {
                listSubData(subDataId, GlobalDict.YESNO_YES, subLevel, minLevel, baseDataType, clear, items);
            }
        }
    }

    /**
     * 返回下级数据ID集合
     * 起始层级为第2层
     *
     * @param parentId     当前权限数据ID
     * @param level        数据权限层级
     * @param baseDataType 基础数据权限类型
     * @return
     */
    private List<Integer> listSubDataId(int parentId, int level, int baseDataType) {
        switch (baseDataType) {
            case DpermitConsts.TYPE_ORGANIZE:
                if (level < DpermitConsts.ORGANIZE_LEVEL_GRANDSON) {
                    return organizeCache.listOrgId(parentId);
                } else {
                    return companyCache.listSpCompanyIdByOrgThrIdAndType(parentId, CompanyConsts.COMPANY_TYPE_SERVICE, GlobalDict.STATUS_ENABLE);
                }
            case DpermitConsts.TYPE_DEPT:
                return deptCache.listDeptId(parentId);
            case DpermitConsts.TYPE_AREA:
                return areaCache.listAreaId(parentId);
            case DpermitConsts.TYPE_COOPERATION:
                if (level == DpermitConsts.COOPERATION_LEVEL_COMPANY) {
                    return channelCache.listChannelIdByPlatAndLevel(parentId, (DpermitConsts.COOPERATION_LEVEL_COMPANY - 1), GlobalDict.STATUS_ENABLE);
                } else if (level == DpermitConsts.COOPERATION_LEVEL_CHANNEL) {
                    return channelCache.listChannelId(parentId);
                }
            case DpermitConsts.TYPE_CUBE:
                return cubeCache.listMenuId(parentId);
            case DpermitConsts.TYPE_PRODUCT:
                return productGroupCache.listByGroupIdOrLineId(parentId, level);
            default:
                return Collections.EMPTY_LIST;
        }
    }
    // endregion

    // region 取得员工角色列表

    @Override
    public List<Integer> listRoleByStaff(Integer staffId, Integer deptId, Integer permitSelf) {

        if (staffId == null) {
            throw new NullPointerException("null staffId");
        }
        if (deptId == null) {
            throw new NullPointerException("null deptId");
        }

        // 查询员工角色
        List<Integer> roleIds = roleStaffService.listRoleIdByStaffId(staffId);
        if (CollectionUtils.isEmpty(roleIds)) {
            // 查询部门角色
            McDept dept = deptCache.getDept(deptId);
            if (dept != null && NumberUtil.isNotNullOrZero(dept.getRoleId())) {
                roleIds.add(dept.getRoleId());
            }
        }

        return roleIds;
    }
    // endregion

    // region 检验员工是否拥有"超级管理员角色"

    @Override
    @ReadOnlyConnection
    public boolean isSuperAdmin(Integer staffId) {

        // 员工有用的角色ID
        List<Integer> staffRoleIds = roleStaffService.listRoleIdByStaffId(staffId);

        // 员工是否拥有"超级管理员角色"
        return staffRoleIds.contains(config.getSuperAdminRoleId());
    }
    // endregion

    // region 检验员工是否拥有"权限管理员角色"

    @Override
    @ReadOnlyConnection
    public boolean isAssignAdmin(Integer staffId) {

        // 员工有用的角色ID
        List<Integer> staffRoleIds = roleStaffService.listRoleIdByStaffId(staffId);

        // 超级管理员自动成为"权限管理员角色"
        if (staffRoleIds.contains(config.getSuperAdminRoleId())) {
            return true;
        }

        // 员工是否拥有"权限管理员角色"
        return staffRoleIds.contains(config.getAssignAdminRoleId());
    }
    // endregion

    // region 检验员工是否拥有"系统全局角色"

    @Override
    @ReadOnlyConnection
    public boolean isGlobalAdmin(Integer staffId) {

        // 员工有用的角色ID
        List<Integer> staffRoleIds = roleStaffService.listRoleIdByStaffId(staffId);

        // 超级管理员自动成为"系统全局角色"
        if (staffRoleIds.contains(config.getSuperAdminRoleId())) {
            return true;
        }

        // 员工是否拥有"系统全局角色"
        List<Integer> globalRoleIds = roleCache.listGlobalRoleId();
        for (int staffRoleId : staffRoleIds) {
            if (globalRoleIds.contains(staffRoleId)) {
                return true;
            }
        }
        return false;
    }
    // endregion

    // region 新增/修改员工

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResponseDTO addStaff(McStaff staff, LoginStaff loginStaff, String ip) {

        try {

            // 校验
            ResponseDTO checkResult = this.checkStaff(staff);
            if (!checkResult.isSuccess()) {
                return checkResult;
            }
            // 生成初始密码
            String password = PASSWORD_PREFIX + (int) ((Math.random() * 9 + 1) * 100000);
            // 初始化
            this.initStaff(staff, password);
            staff.setWhitelist(GlobalDict.YESNO_NO);
            staff.setPermitSelf(GlobalDict.YESNO_NO);
            staff.setDpermitOrgSelf(GlobalDict.YESNO_NO);
            staff.setDpermitDeptSelf(GlobalDict.YESNO_NO);
            staff.setDpermitAreaSelf(GlobalDict.YESNO_NO);
            staff.setDpermitChannelSelf(GlobalDict.YESNO_NO);
            staff.setDpermitCubeSelf(GlobalDict.YESNO_NO);
            staff.setDpermitMeasureSelf(GlobalDict.YESNO_NO);
            staff.setDpermitProductSelf(GlobalDict.YESNO_NO);

            // 根据组织架构查询公司  如果有多个则取第一个
//            CompanyDIO baseCompanyQuery = new CompanyDIO();
//            baseCompanyQuery.setOrgId(staff.getOrgId());
//            ResponseDTO<com.zmn.common.dto2.AMISListData<CompanyDRO>> responseDTO = companyListRemoteService.listPageCompanyDROByQuery(baseCompanyQuery);
//            List<CompanyDRO> baseCompanies = responseDTO.isSuccess() ? responseDTO.getData().getItems() : new ArrayList<>(1);
//            if (CollectionUtil.isNotNullOrEmpty(baseCompanies)) {
//                staff.setCompanyId(baseCompanies.get(0).getCompanyId());
//            }
            McOrganize mcOrganize = Optional.ofNullable(organizeService.findByKey(staff.getOrgId())).orElse(new McOrganize());
            staff.setCompanyId(mcOrganize.getCompanyId());

            if (NumberUtil.isNotNullOrZero(staff.getDeptId())) {
                McDept dept = deptService.findByKey(staff.getDeptId());
                staff.setDeptName(dept == null ? "" : dept.getDeptName());
            }

            logger.info(String.format("[ZMN]新增员工成功,staff:[%s]", JSONObject.toJSONString(staff)));
            String mobile = staff.getMobile();
            staffService.insert(staff);

            // 给新员工发送短信
            this.sendMessage(mobile, staff.getStaffName(), password);

            // 添加员工新增日志
            StaffChangeLog staffChangeLog = new StaffChangeLog();
            staffChangeLog.setStaffId(staff.getStaffId());
            staffChangeLog.setBeforContent("-");
            staffChangeLog.setAfterContent("创建新员工");
            staffChangeLog.setAbutmenterId(loginStaff.getStaffId());
            staffChangeLog.setAbutmenter(loginStaff.getRealName());
            staffChangeLog.setAbutmenterDeptId(loginStaff.getDeptId() + "");
            staffChangeLog.setAbutmenterDeptName(loginStaff.getDeptName());
            staffChangeLog.setChangeType(StaffChangeLogConsts.CHANGE_TYPE_ADD);
            staffChangeLog.setCreateTime(DateUtil.getNow());
            IpInfo ipInfo = staffIpInfoBService.locationByIP(ip);
            if (Objects.nonNull(ipInfo)) {
                staffChangeLog.setAbutmenterCity(ipInfo.getProvince() + "-" + ipInfo.getCity());
            }
            staffChangeLog.setIp(ip);
            staffChangeLogService.insert(staffChangeLog);

            // 更新缓存
            staffCache.setStaff(staff);

            return ResponseDTO.success(staff.getStaffId(), "新增员工成功");

        } catch (Exception ex) {
            logger.error(String.format("[ZMN]新增员工失败,staff:[%s],ex:[%s]", JSONObject.toJSONString(staff), ex.getMessage()));
            return ResponseDTO.fail("新增员工失败");
        }
    }

    @Override
    @Transactional
    public ResponseDTO modifyStaff(McStaff staff, LoginStaff loginStaff, String ip) {

        try {

            // 校验
            ResponseDTO checkResult = this.checkStaff(staff);
            if (!checkResult.isSuccess()) {
                return checkResult;
            }

            // 初始化
            this.initStaff(staff, null);

            // 设置真实姓名拼音
            if (StringUtil.isBlank(staff.getPinyinName())) {
                staff.setPinyinName(PinYinUtil.stringToPinyin(staff.getRealName(), ","));
            }

            // 不能修改为离职状态
            McStaff staffHistory = staffService.findByKey(staff.getStaffId());
            if (staffHistory == null) {
                return ResponseDTO.fail(StatusConsts.STATUS_ERROR, "未找到相应员工！");
            }

            if (Objects.equals(staff.getStaffStatus(), StaffConsts.STAFF_STATUS_LEAVE)) {
                if (!Objects.equals(staffHistory.getStaffStatus(), StaffConsts.STAFF_STATUS_LEAVE)) {
                    return ResponseDTO.fail(StatusConsts.STATUS_ERROR, "不能直接将员工修改为离职！");
                }
            }

            // 根据组织架构查询公司  如果有多个则取第一个
            CompanyDIO companyQuery = new CompanyDIO();
            companyQuery.setOrgId(staff.getOrgId());
            ResponseDTO<AMISListData<CompanyDRO>> responseDTO = companyListRemoteService.listPageCompanyDROByQuery(companyQuery);
            List<CompanyDRO> baseCompanies = responseDTO.isSuccess() ? responseDTO.getData().getItems() : new ArrayList<>(1);
            if (CollectionUtil.isNotNullOrEmpty(baseCompanies)) {
                staff.setCompanyId(baseCompanies.get(0).getCompanyId());
            }

            if (NumberUtil.isNotNullOrZero(staff.getDeptId())) {
                McDept dept = deptService.findByKey(staff.getDeptId());
                staff.setDeptName(dept == null ? "" : dept.getDeptName());
            }

            // 记录操作日志
            this.addStaffChangeLog(staff, staffHistory, loginStaff, ip);

            // 员工信息
            staffService.updateByKey(staff);

            // 自定义功能权限标识
            McRoleStaff roleStaff = new McRoleStaff();
            roleStaff.setStaffId(staff.getStaffId());
            staff.setPermitSelf(roleStaffService.countByQuery(roleStaff) > 0 ? GlobalDict.YESNO_YES : GlobalDict.YESNO_NO);
            staffService.updatePermitSelf(staff);

            // 自定义数据权限标识
            McStaffDpermitQuery dpermitQuery = new McStaffDpermitQuery();
            dpermitQuery.setStaffId(staff.getStaffId());
            dpermitQuery.setDataType(DpermitConsts.TYPE_ORGANIZE);
            staff.setDpermitOrgSelf(staffDpermitService.countByQuery(dpermitQuery) > 0 ? GlobalDict.YESNO_YES : GlobalDict.YESNO_NO);
            dpermitQuery.setDataType(DpermitConsts.TYPE_DEPT);
            staff.setDpermitDeptSelf(staffDpermitService.countByQuery(dpermitQuery) > 0 ? GlobalDict.YESNO_YES : GlobalDict.YESNO_NO);
            dpermitQuery.setDataType(DpermitConsts.TYPE_AREA);
            staff.setDpermitAreaSelf(staffDpermitService.countByQuery(dpermitQuery) > 0 ? GlobalDict.YESNO_YES : GlobalDict.YESNO_NO);
            dpermitQuery.setDataType(DpermitConsts.TYPE_COOPERATION);
            staff.setDpermitChannelSelf(staffDpermitService.countByQuery(dpermitQuery) > 0 ? GlobalDict.YESNO_YES : GlobalDict.YESNO_NO);
            dpermitQuery.setDataType(DpermitConsts.TYPE_CUBE);
            staff.setDpermitCubeSelf(staffDpermitService.countByQuery(dpermitQuery) > 0 ? GlobalDict.YESNO_YES : GlobalDict.YESNO_NO);
            dpermitQuery.setDataType(DpermitConsts.TYPE_MEASURE);
            staff.setDpermitMeasureSelf(staffDpermitService.countByQuery(dpermitQuery) > 0 ? GlobalDict.YESNO_YES : GlobalDict.YESNO_NO);
            dpermitQuery.setDataType(DpermitConsts.TYPE_PRODUCT);
            staff.setDpermitProductSelf(staffDpermitService.countByQuery(dpermitQuery) > 0 ? GlobalDict.YESNO_YES : GlobalDict.YESNO_NO);
            staffService.updateDpermitSelf(staff);

            // 更新缓存
            McStaff mcStaff = staffService.findByKey(staff.getStaffId());
            staffCache.setStaff(mcStaff);
            // 发送员工修改消息
            sendStaffMessage(staff);
            logger.info(String.format("[ZMN]修改员工成功,staff:[%s]", JSONObject.toJSONString(staff)));
            return ResponseDTO.success(staff.getStaffId(), "修改员工成功");

        } catch (Exception ex) {
            logger.error(String.format("[ZMN]修改员工失败,staff:[%s],ex:[%s]", JSONObject.toJSONString(staff), ex.getMessage()));
            return ResponseDTO.fail("修改员工失败");
        }
    }

    /**
     * 校验员工信息
     *
     * @param staff
     * @return
     */
    private ResponseDTO checkStaff(McStaff staff) {

        /**
         * 校验参数
         */
        if (StringUtil.isBlank(staff.getStaffName())) {
            return ResponseDTO.fail("账号不能为空");
        }
        if (StringUtil.isBlank(staff.getRealName())) {
            return ResponseDTO.fail("真实姓名不能为空");
        }
        if (StringUtil.isBlank(staff.getMobile())) {
            return ResponseDTO.fail("手机号码不能为空");
        }
        if (StringUtil.isBlank(staff.getMail())) {
            return ResponseDTO.fail("邮箱不能为空");
        }
        if (NumberUtil.isNullOrZero(staff.getPlat()) || staff.getPlat() <= GlobalConsts.TOP_ID) {
            return ResponseDTO.fail("未指定所属组织，请至少指定1级组织");
        }

        /**
         * 校验账号/手机/邮箱唯一性
         * 只校验正常账号范围内唯一(status=2)
         */
        if (staffService.findStaffNameExists(staff)) {
            return ResponseDTO.fail("账号已存在");
        }
        if (staffService.findMobileExists(staff)) {
            return ResponseDTO.fail("手机号码已存在");
        }
        if (staffService.findMailExists(staff)) {
            return ResponseDTO.fail("邮箱已存在");
        }
        if (StringUtil.isNotBlank(staff.getStaffNumber()) && (!staff.getStaffNumber().equals("0"))) {
            if (staffService.findStaffNumberExists(staff)) {
                return ResponseDTO.fail("工号已存在");
            }
        }

        return ResponseDTO.success(null, "OK");
    }

    /**
     * 初始化员工
     *
     * @param staff
     */
    private void initStaff(McStaff staff, String password) {

        /**
         * 组织架构
         */
        int orgId = staff.getOrgId() != null ? staff.getOrgId() : 0;
        if (orgId <= GlobalConsts.TOP_ID) {
            orgId = NumberUtil.isNotNullOrZero(staff.getOrgTwoId()) ? staff.getOrgTwoId() : 0;
        }
        if (orgId <= GlobalConsts.TOP_ID) {
            orgId = NumberUtil.isNotNullOrZero(staff.getPlat()) ? staff.getPlat() : 0;
        }
        staff.setOrgId(orgId);

        // 默认zmn
        if (NumberUtil.isNullOrZero(staff.getPlat())) {
            staff.setPlat(GlobalConsts.PLAT_MARK_ZMN);
        }

        /**
         * 所属部门
         */
//        int deptId = staff.getDeptThrId() != null ? staff.getDeptThrId() : 0;
//        if (deptId <= GlobalConsts.TOP_ID) {
//            deptId = NumberUtil.isNotNullOrZero(staff.getDeptTwoId()) ? staff.getDeptTwoId() : 0;
//        }
//        if (deptId <= GlobalConsts.TOP_ID) {
//            deptId = NumberUtil.isNotNullOrZero(staff.getDeptOneId()) ? staff.getDeptOneId() : 0;
//        }
//        staff.setDeptId(deptId);

        if (NumberUtil.isNotNullOrZero(staff.getDeptId()) && staff.getDeptId() > GlobalConsts.TOP_ID) {
            McDept dept = deptService.findByKey(staff.getDeptId());
            if (null != dept) {
                staff.setDeptName(dept.getDeptName());
            }

        }

        /**
         * 姓名拼音
         */
        if (StringUtil.isBlank(staff.getPinyinName()) && StringUtil.isNotBlank(staff.getRealName())) {
            staff.setPinyinName(PinYinUtil.stringToPinyin(staff.getRealName(), GlobalConsts.SPLITER_SPACE));
        }

        /**
         * 默认密码，新增员工时生成
         */
        if (NumberUtil.isNullOrZero(staff.getStaffId())) {

            // 加密盐值
            String salt = this.generateSalt();

            // 创建密码
            staff.setSalt(salt);
            String defaultPassword = passwordService.generatePassword(password, salt);
            staff.setPassword(defaultPassword);

            // 初始密码标识
            staff.setInitPwd(GlobalDict.YESNO_YES);
        }
    }
    // endregion

    // region 设置临时密码

    @Override
    public ResponseDTO setTempPwd(Integer staffId, String tempPwd, LoginStaff loginStaff, String ip, String tmpPwdLogin) {

        if (NumberUtil.isNullOrZero(staffId)) {
            return ResponseDTO.fail("未指定员工");
        }
        if (StringUtil.isBlank(tempPwd)) {
            return ResponseDTO.fail("临时密码不能为空");
        }
        staffCache.setTempPwd(staffId, tempPwd);
        // 新增日志
        StaffChangeLog staffChangeLog = new StaffChangeLog();
        staffChangeLog.setStaffId(staffId);
        staffChangeLog.setBeforContent("-");
        staffChangeLog.setAfterContent("设置成功");
        staffChangeLog.setAbutmenterId(loginStaff.getStaffId());
        staffChangeLog.setAbutmenter(loginStaff.getRealName());
        staffChangeLog.setAbutmenterDeptId(loginStaff.getDeptId() + "");
        staffChangeLog.setAbutmenterDeptName(loginStaff.getDeptName());
        staffChangeLog.setChangeType(StaffChangeLogConsts.CHANGE_TYPE_TMP_PASS);
        staffChangeLog.setCreateTime(DateUtil.getNow());
        IpInfo ipInfo = staffIpInfoBService.locationByIP(ip);
        if (Objects.nonNull(ipInfo)) {
            staffChangeLog.setAbutmenterCity(ipInfo.getProvince() + "-" + ipInfo.getCity());
        }
        staffChangeLog.setIp(ip);
        staffChangeLogService.insert(staffChangeLog);
        //设置临时登录跳转页面
        String gotoUrl = tmpPwdLogin + "?logId=" + staffChangeLog.getLogId();
        return ResponseDTO.success(gotoUrl, "临时密码设置成功");
    }
    // endregion

    // region 重设密码

    @Override
    public String resetPwd(String staffIds, LoginStaff loginStaff, String ip) {

        if (StringUtils.isEmpty(staffIds)) {
            return SystemConsts.STAFF_PWD_DEFAULT;
        }

        String[] ids = staffIds.split(",");
        for (String id : ids) {
            McStaff staffDb = staffService.findByKey(Integer.valueOf(id));
            String defaultPassword = passwordService.generatePassword(SystemConsts.STAFF_PWD_DEFAULT, staffDb.getSalt());
            staffService.updateInitPasswordByKey(defaultPassword, loginStaff, staffDb.getStaffId());

            // 新增日志
            StaffChangeLog staffChangeLog = new StaffChangeLog();
            staffChangeLog.setStaffId(Integer.valueOf(id));
            staffChangeLog.setBeforContent("-");
            staffChangeLog.setAfterContent("重置成功");
            staffChangeLog.setAbutmenterId(loginStaff.getStaffId());
            staffChangeLog.setAbutmenter(loginStaff.getRealName());
            staffChangeLog.setAbutmenterDeptId(loginStaff.getDeptId() + "");
            staffChangeLog.setAbutmenterDeptName(loginStaff.getDeptName());
            staffChangeLog.setChangeType(StaffChangeLogConsts.CHANGE_TYPE_REST_PASS);
            staffChangeLog.setCreateTime(DateUtil.getNow());
            IpInfo ipInfo = staffIpInfoBService.locationByIP(ip);
            if (Objects.nonNull(ipInfo)) {
                staffChangeLog.setAbutmenterCity(ipInfo.getProvince() + "-" + ipInfo.getCity());
            }
            staffChangeLog.setIp(ip);
            staffChangeLogService.insert(staffChangeLog);
        }

        return SystemConsts.STAFF_PWD_DEFAULT;
    }
    // endregion

    // region 设置不限IP日期

    @Override
    public ResponseDTO setOpenDate(Integer staffId, String dateRange, LoginStaff loginStaff, String ip) {

        if (NumberUtil.isNullOrZero(staffId)) {
            return ResponseDTO.fail("未指定员工");
        }

        // 基础数据v1.11.0需求清单  员工不限ip,支持在生效期内进行人工清空操作保存
//        if (StringUtil.isBlank(dateRange)) {
//            return ResponseDTO.fail("未指定日期范围");
//        }

        // 时间为空代表清除不限ip
        if (StringUtil.isBlank(dateRange)) {
            staffCache.cancelOpenDate(staffId);
            return ResponseDTO.success(null, "不限IP日期设置成功!");
        }

        /**
         * 解析开始日期和结束日期
         */
        Date startTime, endTime;

        List<Date> lastLoginTimeRang = parseDateRangeStr(dateRange);
        if (lastLoginTimeRang.size() > 0) {
            startTime = lastLoginTimeRang.get(0);
        } else {
            return ResponseDTO.fail("未指定开始日期");
        }
        if (lastLoginTimeRang.size() > 1) {
            endTime = lastLoginTimeRang.get(1);
        } else {
            return ResponseDTO.fail("未指定结束日期");
        }

        // 如开始时间为今天，则从现在开始
        String startTimeStr = DateUtil.toString(startTime, DateUtil.FORMAT_DATE3);
        String todayStr = DateUtil.toString(DateUtil.getNow(), DateUtil.FORMAT_DATE3);
        if (startTimeStr.equals(todayStr)) {
            startTime = DateUtil.getNow();
        }

        /**
         * 校验日期区间有效性
         */
        if (endTime.getTime() < startTime.getTime()) {
            return ResponseDTO.fail("结束日期必须在开始日期或当前日期之后");
        }
        long timespan = DateUtil.getTimespan(startTime, endTime, DateUtil.UNIT_SECOND);

        // 特殊处理（如：过年、疫情...）
        Date limitDate = DateUtil.getDateStart("20220630", DateUtil.FORMAT_DATE2);


        //  可以超过两天
        if (endTime.getTime() < limitDate.getTime()) {
            if (timespan > 60 * 60 * 24 * 7) {
                return ResponseDTO.fail("不限IP日期不能超过7天");
            }
        } else if (timespan > 60 * 60 * 24 * 2) {
            return ResponseDTO.fail("不限IP日期不能超过2天");
        }

        // 记录员工异动信息
        handleStaffChangeLogNotLimitIPDate(staffId, startTime, endTime, loginStaff, ip);
        // 开始日期与结束日期的时长，单位：秒
        Date now = new Date();
        if (endTime.getTime() < now.getTime()) {
            return ResponseDTO.success(null, "不限IP日期设置成功");
        }

        long timeSpan = DateUtil.getTimespan(now, endTime, DateUtil.UNIT_SECOND);
        staffCache.setOpenDate(staffId, dateRange, (int) timeSpan);

        return ResponseDTO.success(null, "不限IP日期设置成功");
    }

    @Override
    public ResponseDTO batchSetOpenDate(List<Integer> staffIds, String dateRange, LoginStaff loginStaff, String ip) {
        if (CollectionUtil.isNullOrEmpty(staffIds)) {
            return ResponseDTO.fail("未指定员工");
        }

        // 时间为空代表清除不限ip
        if (StringUtil.isBlank(dateRange)) {

            staffIds.forEach(x -> staffCache.cancelOpenDate(x));
            return ResponseDTO.success(null, "不限IP日期设置成功!");
        }

        /**
         * 解析开始日期和结束日期
         */
        Date startTime, endTime;

        List<Date> lastLoginTimeRang = parseDateRangeStr(dateRange);
        if (lastLoginTimeRang.size() > 0) {
            startTime = lastLoginTimeRang.get(0);
        } else {
            return ResponseDTO.fail("未指定开始日期");
        }
        if (lastLoginTimeRang.size() > 1) {
            endTime = lastLoginTimeRang.get(1);
        } else {
            return ResponseDTO.fail("未指定结束日期");
        }

        // 如开始时间为今天，则从现在开始
        String startTimeStr = DateUtil.toString(startTime, DateUtil.FORMAT_DATE3);
        String todayStr = DateUtil.toString(DateUtil.getNow(), DateUtil.FORMAT_DATE3);
        if (startTimeStr.equals(todayStr)) {
            startTime = DateUtil.getNow();
        }

        /**
         * 校验日期区间有效性
         */
        if (endTime.getTime() < startTime.getTime()) {
            return ResponseDTO.fail("结束日期必须在开始日期或当前日期之后");
        }

        long timespan = DateUtil.getTimespan(startTime, endTime, DateUtil.UNIT_SECOND);

        // 特殊处理（如：过年、疫情...）
        Date limitDate = DateUtil.getDateStart("20220630", DateUtil.FORMAT_DATE2);


        //  可以超过两天
        if (endTime.getTime() < limitDate.getTime()) {
            if (timespan > 60 * 60 * 24 * 7) {
                return ResponseDTO.fail("不限IP日期不能超过7天");
            }
        } else if (timespan > 60 * 60 * 24 * 2) {
            return ResponseDTO.fail("不限IP日期不能超过2天");
        }

        // 记录员工异动信息
        batchHandleStaffChangeLogNotLimitIPDate(staffIds, startTime, endTime, loginStaff, ip);
        // 开始日期与结束日期的时长，单位：秒
        Date now = new Date();
        if (endTime.getTime() < now.getTime()) {
            return ResponseDTO.success(null, "不限IP日期设置成功");
        }

        long timeSpan = DateUtil.getTimespan(now, endTime, DateUtil.UNIT_SECOND);
        staffIds.forEach(x -> staffCache.setOpenDate(x, dateRange, (int) timeSpan));


        return ResponseDTO.success(null, "不限IP日期设置成功");
    }

    // endregion

    // region 设置IP白名单

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResponseDTO setWhitelist(Integer staffId, Integer whitelist, LoginStaff loginStaff, String ip) {

        if (NumberUtil.isNullOrZero(staffId)) {
            return ResponseDTO.fail("未指定员工");
        }
        if (NumberUtil.isNullOrZero(whitelist)) {
            whitelist = GlobalDict.YESNO_NO;
        }

        // 添加员工日志
        McStaff mcStaff = staffService.findByKey(staffId);
        if (mcStaff != null) {
            if (!Objects.equals(whitelist, mcStaff.getWhitelist())) {
                StaffChangeLog staffChangeLog = new StaffChangeLog();
                staffChangeLog.setStaffId(staffId);
                staffChangeLog.setBeforContent(NumberUtil.isNullOrZero(mcStaff.getWhitelist()) ? "-" : GlobalDict.getYesOrNo(mcStaff.getWhitelist()));
                staffChangeLog.setAfterContent(GlobalDict.getYesOrNo(whitelist));
                staffChangeLog.setAbutmenterId(loginStaff.getStaffId());
                staffChangeLog.setAbutmenter(loginStaff.getRealName());
                staffChangeLog.setAbutmenterDeptId(loginStaff.getDeptId() + "");
                staffChangeLog.setAbutmenterDeptName(loginStaff.getDeptName());
                staffChangeLog.setChangeType(StaffChangeLogConsts.CHANGE_TYPE_IP);
                staffChangeLog.setCreateTime(DateUtil.getNow());
                staffChangeLog.setIp(ip);
                IpInfo ipInfo = staffIpInfoBService.locationByIP(ip);
                if (Objects.nonNull(ipInfo)) {
                    staffChangeLog.setAbutmenterCity(ipInfo.getProvince() + "-" + ipInfo.getCity());
                }
                staffChangeLogService.insert(staffChangeLog);
            }
        }

        staffService.updateWhitelist(staffId, whitelist, loginStaff.getRealName());
        return ResponseDTO.success(null, "IP白名单设置成功");
    }
    // endregion

    // region 绑定工程师

    @Override
    public ResponseDTO bindMaster(Integer staffId, List<Integer> masterIds, String updater, String ip) {

        if (NumberUtil.isNullOrZero(staffId)) {
            return ResponseDTO.fail("未指定员工");
        }
        if (CollectionUtil.isNullOrEmpty(masterIds) || masterIds.size() != 4) {
            return ResponseDTO.fail("指定工程师参数错误");
        }

        return ResponseDTO.success(null, "工程师绑定成功");
    }
    // endregion

    // region 修改员工自定义权限标识

    @Override
    public ResponseDTO setPermitSelf(Integer staffId, String updater) {
        McRoleStaff roleStaff = new McRoleStaff();
        roleStaff.setStaffId(staffId);
        Integer permitSelf = (roleStaffService.countByQuery(roleStaff) > 0 ? GlobalDict.YESNO_YES : GlobalDict.YESNO_NO);
        return this.setPermitSelf(staffId, permitSelf, updater);
    }

    @Override
    public ResponseDTO setPermitSelf(Integer staffId, Integer permitSelf, String updater) {

        try {
            // 修改功能权限需要清除staff缓存
            staffCache.deleteStaff(staffId);
            staffService.updatePermitSelf(staffId, permitSelf, updater);

            logger.info(String.format("[ZMN]修改员工自定义功能权限标识成功,staffId:[%s],updater:[%s]", staffId, updater));
            return ResponseDTO.success(staffId, "修改员工自定义功能权限标识成功");

        } catch (Exception ex) {
            logger.error(String.format("[ZMN]修改员工自定义功能权限标识出错,staffId:[%s],updater:[%s],ex:[%s]", staffId, updater, ex.getMessage()));
            return ResponseDTO.fail("修改员工自定义功能权限标识失败");
        }
    }

    @Override
    public ResponseDTO setDpermitSelf(Integer staffId, Integer dpermitType, String updater) {
        McStaffDpermitQuery dpermitQuery = new McStaffDpermitQuery();
        dpermitQuery.setStaffId(staffId);
        dpermitQuery.setDataType(dpermitType);
        Integer dpermitSelf = (staffDpermitService.countByQuery(dpermitQuery) > 0 ? GlobalDict.YESNO_YES : GlobalDict.YESNO_NO);
        return this.setDpermitSelf(staffId, dpermitType, dpermitSelf, updater);
    }

    @Override
    public ResponseDTO setDpermitSelf(Integer staffId, Integer dpermitType, Integer dpermitSelf, String updater) {

        try {

            switch (dpermitType) {
                case DpermitConsts.TYPE_ORGANIZE:
                    staffService.updateDpermitOrgSelf(staffId, dpermitSelf, updater);
                    break;
                case DpermitConsts.TYPE_DEPT:
                    staffService.updateDpermitDeptSelf(staffId, dpermitSelf, updater);
                    break;
                case DpermitConsts.TYPE_AREA:
                    staffService.updateDpermitAreaSelf(staffId, dpermitSelf, updater);
                    break;
                case DpermitConsts.TYPE_COOPERATION:
                    staffService.updateDpermitCooperationSelf(staffId, dpermitSelf, updater);
                    break;
                case DpermitConsts.TYPE_CUBE:
                    staffService.updateDpermitCubeSelf(staffId, dpermitSelf, updater);
                    break;
                case DpermitConsts.TYPE_MEASURE:
                    staffService.updateDpermitMeasureSelf(staffId, dpermitSelf, updater);
                    break;
                case DpermitConsts.TYPE_PRODUCT:
                    staffService.updateDpermitProductSelf(staffId, dpermitSelf, updater);
                    break;
                case DpermitConsts.TYPE_PROMOTION:
                    staffService.updateDpermitPromotionSelf(staffId, dpermitSelf, updater);
                    break;
                case DpermitConsts.TYPE_CHANNEL:
                    staffService.updateDpermitChannelSelf(staffId, dpermitSelf, updater);
                    break;
            }

            // 更新员工
            staffCache.setStaff(staffId);

            logger.info(String.format("[ZMN]修改员工自定义数据权限标识成功,staffId:[%s],dpermitType:[%s],updater:[%s]", staffId, dpermitType, updater));
            return ResponseDTO.success(staffId, "修改员工自定义数据权限标识成功");

        } catch (Exception ex) {
            logger.error(String.format("[ZMN]修改员工自定义数据权限标识出错,staffId:[%s],dpermitType:[%s],updater:[%s],ex:[%s]", staffId, dpermitType, updater, ex.getMessage()));
            return ResponseDTO.fail("修改员工自定义数据权限标识失败");
        }
    }
    // endregion

    @Override
    public void synchroStaffByStaffDingTalkAndStaff(McStaff staff, StaffDingTalk staffDingTalk) {
        this.synchroByDingTalk(staff, staffDingTalk);
        staffArchivesService.synchroByDingTalk(staff.getStaffId(), staffDingTalk);
        staffContractService.synchroByDingTalk(staff.getStaffId(), staffDingTalk);
        if (Objects.nonNull(staffDingTalk.getFamilyMobile())) {
            staffFamilyService.synchroByDingTalk(staff.getStaffId(), staffDingTalk);
        }
    }

    /**
     * 生成密码盐值
     *
     * @return
     */
    @Override
    public String generateSalt() {
        return RandomUtil.getStringRandom(StaffConsts.SALT_LENGTH);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @SuppressWarnings("Duplicates")
    public ResponseDTO importExcel(InputStream inputStream, String operator) throws IOException {
        int failureLine = 0;

        List read = ExcelUtil.read(inputStream, true, false);
        ArrayList<StaffPerformance> staffPerformances = Lists.newArrayListWithExpectedSize(read.size());
        ArrayList<List> failData = Lists.newArrayList();

        List<Integer> staffNumbers = getStaffNumberList(read);
        McStaffQuery staffQuery = new McStaffQuery();
        staffQuery.setStaffNumberList(staffNumbers);
        List<McStaff> staffList = staffService.listByQuery(staffQuery);
        Map<String, McStaff> staffMap = staffList.stream().filter(e -> (StringUtil.isNotBlank(e.getStaffNumber()) && !e.getStaffNumber().equals("0"))).collect(Collectors.toMap(McStaff::getStaffNumber, staff -> staff));

        for (Object readList : read) {
            ArrayList arrayList = (ArrayList) readList;
            StaffPerformance staffPerformance = new StaffPerformance();
            Object staffNumber = Optional.ofNullable(arrayList.get(0)).orElse(null);
            Object staffName = Optional.ofNullable(arrayList.get(1)).orElse(null);
            Object examineStartTime = Optional.ofNullable(arrayList.get(2)).orElse("");
            Object examineEndTime = Optional.ofNullable(arrayList.get(3)).orElse("");
            Object examineType = Optional.ofNullable(arrayList.get(4)).orElse(null);
            Object examineScore = Optional.ofNullable(arrayList.get(5)).orElse(null);
            Object remark = Optional.ofNullable(arrayList.get(6)).orElse(null);

            String staffNumberStr = "";
            if (Objects.nonNull(staffNumber)) {
                try {
                    if (staffNumber != null) {
                        staffNumberStr = staffNumber.toString();
                    }
                    String[] staffNumberArray = staffNumberStr.split("\\.");
                    if (staffNumberArray != null && staffNumberArray.length > 0) {
                        staffNumberStr = staffNumberArray[0];
                    }
                    if (staffMap.get(staffNumberStr) != null && Objects.equals(staffMap.get(staffNumberStr).getRealName(), staffName.toString())) {
                        staffPerformance.setStaffNumber(staffNumberStr);
                        staffPerformance.setStaffId(0);
                        staffPerformance.setCreater(operator);
                        staffPerformance.setCreateTime(DateUtil.getNow());
                        staffPerformance.setUpdater(operator);
                        staffPerformance.setUpdateTime(DateUtil.getNow());
                        staffPerformance.setRemark(remark.toString());
                        staffPerformance.setExamineStartTime(DateUtil.parse(examineStartTime.toString(), DateUtil.FORMAT_DATE));
                        staffPerformance.setExamineEndTime(DateUtil.parse(examineEndTime.toString(), DateUtil.FORMAT_DATE));
                        staffPerformance.setExamineType(Optional.ofNullable(examineType).orElse("").toString());
                        staffPerformance.setExamineScore(Double.valueOf(examineScore.toString()).intValue());
                        staffPerformances.add(staffPerformance);
                    } else {
                        failureLine++;
                        ArrayList<String> innerFailData = new ArrayList<>();
                        innerFailData.add(staffNumber == null ? "" : staffNumberStr);
                        innerFailData.add(staffName == null ? "" : staffName.toString());
                        innerFailData.add(examineStartTime == null ? "" : examineStartTime.toString());
                        innerFailData.add(examineEndTime == null ? "" : examineEndTime.toString());
                        innerFailData.add(examineType == null ? "" : examineType.toString());
                        innerFailData.add(examineScore == null ? "" : String.valueOf(Double.valueOf(examineScore.toString()).intValue()));
                        innerFailData.add(remark == null ? "" : remark.toString());
                        failData.add(innerFailData);
                    }

                } catch (Exception e) {
                    logger.error("[{}],格式化员工id异常，跳过该行数据！,staffNumber:{}", TAG, staffNumber, e);
                    failureLine++;
                    ArrayList<String> innerFailData = new ArrayList<>();
                    innerFailData.add(staffNumber == null ? "" : staffNumberStr);
                    innerFailData.add(staffName == null ? "" : staffName.toString());
                    innerFailData.add(examineStartTime == null ? "" : examineStartTime.toString());
                    innerFailData.add(examineEndTime == null ? "" : examineEndTime.toString());
                    innerFailData.add(examineType == null ? "" : examineType.toString());
                    innerFailData.add(examineScore == null ? "" : String.valueOf(Double.valueOf(examineScore.toString()).intValue()));
                    innerFailData.add(remark == null ? "" : remark.toString());
                    failData.add(innerFailData);
                }
            } else {
                failureLine++;
                ArrayList<String> innerFailData = new ArrayList<>();
                innerFailData.add("");
                innerFailData.add(staffName == null ? "" : staffName.toString());
                innerFailData.add(examineStartTime == null ? "" : examineStartTime.toString());
                innerFailData.add(examineEndTime == null ? "" : examineEndTime.toString());
                innerFailData.add(examineType == null ? "" : examineType.toString());
                innerFailData.add(examineScore == null ? "" : String.valueOf(Double.valueOf(examineScore.toString()).intValue()));
                innerFailData.add(remark == null ? "" : remark.toString());
                failData.add(innerFailData);
            }
        }

        if (!CollectionUtil.isNullOrEmpty(staffPerformances)) {
            staffPerformanceService.insertBatch(staffPerformances);
            staffPerformanceService.updateStaffId();
        }

        if (CollectionUtil.isNullOrEmpty(staffPerformances) && failureLine > 0) {
            return ResponseDTO.fail(String.format("导入数据异常：成功0条,失败%s条", failureLine));
        } else {
            return ResponseDTO.success(failData, String.format("共导入%s个,成功%s个,失败%s个", read.size(), staffPerformances.size(), failureLine));
        }
    }

    @Override
    public ExportExcel<McStaffExportExcelVO> exportExcel(McStaffQuery query) {
        query.setStaffName(query.getKeyword());

        List<Date> lastLoginTimeRang = DateUtil.parseStandardDateRang(query.getLastLoginTimeRang());
        if (lastLoginTimeRang.size() > 0) {
            query.setLastLoginTimeBegin(lastLoginTimeRang.get(0));
        }
        if (lastLoginTimeRang.size() > 1) {
            query.setLastLoginTimeEnd(lastLoginTimeRang.get(1));
        }

        List<Date> entryTimeRange = DateUtil.parseStandardDateRang(query.getEntryTimeRange());
        if (entryTimeRange.size() > 0) {
            query.setEntryTimeStart(entryTimeRange.get(0));
        }
        if (entryTimeRange.size() > 1) {
            query.setEntryTimeEnd(entryTimeRange.get(1));
        }

        List<Date> becomeFormalTimeRange = DateUtil.parseStandardDateRang(query.getBecomeFormalTimeRange());
        if (becomeFormalTimeRange.size() > 0) {
            query.setBecomeFormalTimeStart(becomeFormalTimeRange.get(0));
        }
        if (becomeFormalTimeRange.size() > 1) {
            query.setBecomeFormalTimeEnd(becomeFormalTimeRange.get(1));
        }

        List<McStaffVO> items = staffService.listPageInfoByQuery(query);
        List<McStaffExportExcelVO> exportExcelVOS = new ArrayList<>();
        items.forEach(e -> {
            McStaffExportExcelVO mcStaffExportExcelVO = new McStaffExportExcelVO();
            BeanUtils.copyProperties(e, mcStaffExportExcelVO);
            mcStaffExportExcelVO.setPositionLevelStr(e.getPositionLevel() == null ? "" : StaffDict.getLevelName(e.getPositionLevel()));
            mcStaffExportExcelVO.setSexTypeStr(e.getSexType() == null ? "" : StaffDict.getSexTypeName(e.getSexType()));
            mcStaffExportExcelVO.setRoleTypeName(e.getType() == null ? "" : StaffDict.getRoleTypeName(e.getType()));
            mcStaffExportExcelVO.setEntryTimeStr(DateUtil.subDate(e.getEntryTime()));
            mcStaffExportExcelVO.setBecomeFormalTimeStr(DateUtil.subDate(e.getBecomeFormalTime()));
            mcStaffExportExcelVO.setStaffStatusStr(e.getStaffStatus() == null ? "" : StaffDict.getStatusName(e.getStaffStatus()));
            mcStaffExportExcelVO.setLastLoginTimeStr(DateUtil.subDate(e.getLastLoginTime()));
            mcStaffExportExcelVO.setCreaterAndCreateTime(e.getCreater() + "/" + DateUtil.toString(e.getCreateTime()));
            mcStaffExportExcelVO.setUpdaterAndUpdateTime(e.getUpdater() + "/" + DateUtil.toString(e.getUpdateTime()));
            mcStaffExportExcelVO.setDeptName(e.getDeptName() == null ? e.getPositionName() : e.getDeptName());
            exportExcelVOS.add(mcStaffExportExcelVO);
        });

        // 封装Excel对象
        LinkedHashMap<String, String> fieldMap = Maps.newLinkedHashMap();

        fieldMap.put("sort", "排序");
        fieldMap.put("staffId", "员工ID");
        fieldMap.put("staffName", "员工账号");
        fieldMap.put("realName", "真实姓名");
        fieldMap.put("staffNumber", "工号");
        fieldMap.put("dTalkId", "钉钉UserID");
        fieldMap.put("companyName", "合同公司");
        fieldMap.put("deptName", "部门");
        fieldMap.put("positionLevelStr", "职级");
        fieldMap.put("sexTypeStr", "性别");
        fieldMap.put("roleTypeName", "员工类型");
        fieldMap.put("entryTimeStr", "入职时间");
        fieldMap.put("becomeFormalTimeStr", "转正时间");
        fieldMap.put("staffStatusStr", "状态");
        fieldMap.put("lastLoginTimeStr", "最近登录");
        fieldMap.put("createrAndCreateTime", "创建人/创建时间");
        fieldMap.put("updaterAndUpdateTime", "修改人/修改时间");

        ExportExcel<McStaffExportExcelVO> excel = new ExportExcel(exportExcelVOS, fieldMap, true);

        return excel;
    }

    @Override
    public Boolean enableLogin(Integer logId) {
        StaffChangeLog changeLog = staffChangeLogService.findById(logId);
        if (Objects.isNull(changeLog)) {
            return false;
        }
        Date logTime = changeLog.getCreateTime();
        if (DateUtil.getTimespan(logTime, new Date(), DateUtil.UNIT_SECOND) > 60 * 60) {
            return false;
        }
        return true;
    }

    /**
     * 描述: 根据员工id集合查询员工查询前溯天数
     *
     * @param staffIdList 员工id集合
     * @return List<StaffIdAndDayLimitDRO>
     * @author xujie
     * @since 2021/05/28
     **/
    @Override
    public List<StaffIdAndDayLimitDRO> listStaffIdAndDayLimitByStaffIdList(List<Integer> staffIdList) {

        List<McRoleStaff> roleStaffList = roleStaffService.listRoleStaffByStaffIdList(staffIdList);
        // 员工对应角色列表
        Map<Integer, List<Integer>> staffIdAndRoleIdMap = roleStaffList.stream().collect(Collectors.groupingBy(McRoleStaff::getStaffId, Collectors.mapping(McRoleStaff::getRoleId, Collectors.toList())));
        List<Integer> staffIdListRole = roleStaffList.stream().map(McRoleStaff::getStaffId).collect(Collectors.toList());
        List<Integer> staffIdListNotRole = staffIdList.stream().filter(s -> !staffIdListRole.contains(s)).collect(Collectors.toList());
        if (staffIdListNotRole.size() > 0) {
            List<StaffIdAndDeptId> staffIdAndDeptIdList = staffService.listStaffIdAndDeptIdByStaffIdList(staffIdListNotRole);
            List<Integer> deptIdList = staffIdAndDeptIdList.stream().map(StaffIdAndDeptId::getDeptId).collect(Collectors.toList());
            List<DeptIdAndRoleId> deptIdAndRoleIdList = deptService.listDeptIdAndRoleIdByDeptIdList(deptIdList);
            Map<Integer, Integer> deptIdAndRoleIdMap = deptIdAndRoleIdList.stream().filter(d -> NumberUtil.isNotNullOrZero(d.getRoleId()))
                    .collect(Collectors.toMap(DeptIdAndRoleId::getDeptId, DeptIdAndRoleId::getRoleId, (e1, e2) -> e2));
            staffIdAndDeptIdList.forEach(s -> {
                Integer roleId = deptIdAndRoleIdMap.get(s.getDeptId());
                if (roleId != null) {
                    staffIdAndRoleIdMap.put(s.getStaffId(), Collections.singletonList(roleId));
                }
            });
        }
        HashSet<Integer> roleIdSet = staffIdAndRoleIdMap.values().stream().collect(HashSet::new, HashSet::addAll, HashSet::addAll);
        List<RoleIdAndDayLimit> roleIdAndDayLimitList = roleService.listRoleIdAndDayLimitByRoleIdListAndStatus(new ArrayList<>(roleIdSet), GlobalDict.STATUS_ENABLE);
        Map<Integer, Integer> roleIdAndDayLimitMap = roleIdAndDayLimitList.stream().collect(Collectors.toMap(RoleIdAndDayLimit::getRoleId, RoleIdAndDayLimit::getDayLimit, (e1, e2) -> e2));
        List<StaffIdAndDayLimitDRO> staffIdAndDayLimitList = new ArrayList<>(staffIdList.size());
        staffIdList.forEach(staffId -> {
            StaffIdAndDayLimitDRO staffIdAndDayLimit = new StaffIdAndDayLimitDRO();
            staffIdAndDayLimit.setStaffId(staffId);
            List<Integer> roleIdList = staffIdAndRoleIdMap.get(staffId);
            // 查询前溯天数
            int dayLimit = 0;
            if (roleIdList != null && roleIdList.size() > 0) {
                for (Integer roleId : roleIdList) {
                    Integer limit = Optional.ofNullable(roleIdAndDayLimitMap.get(roleId)).orElse(0);
                    if (limit > dayLimit) {
                        dayLimit = limit;
                    }
                }
            }
            if (dayLimit > config.getDataDayLimit()) {
                dayLimit = config.getDataDayLimit();
            }
            staffIdAndDayLimit.setDayLimit(dayLimit);
            staffIdAndDayLimitList.add(staffIdAndDayLimit);
        });
        return staffIdAndDayLimitList;
    }


    private void synchroByDingTalk(McStaff staff, StaffDingTalk staffDingTalk) {
        if (Objects.isNull(staff.getBecomeFormalTime()) && Objects.nonNull(staffDingTalk.getBecomeFormalTime())) {
            staff.setBecomeFormalTime(staffDingTalk.getBecomeFormalTime());
        }
        if (Objects.isNull(staff.getType()) && Objects.nonNull(staffDingTalk.getRoleType())) {
            staff.setType(staffDingTalk.getRoleType());
        }

        staffService.updateByKey(staff);
        sendStaffMessage(staff);
    }

    private List<Integer> getStaffNumberList(List data) {
        List<Integer> staffNumberList = new ArrayList<>();
        for (Object readList : data) {
            ArrayList arrayList = (ArrayList) readList;
            try {
                Integer realStaffNumber = Double.valueOf(arrayList.get(0).toString()).intValue();
                staffNumberList.add(realStaffNumber);
            } catch (Exception e) {
                logger.error("批量导入员工绩效-员工工号错误：{}", ((ArrayList) readList).get(0), e);
            }
        }

        return staffNumberList;
    }

    private void addStaffChangeLog(McStaff staff, McStaff staffHistory, LoginStaff loginStaff, String ip) {

        IpInfo ipInfo = staffIpInfoBService.locationByIP(ip);
        String abutmenterCity = null;
        if (Objects.nonNull(ipInfo)) {
            abutmenterCity = ipInfo.getProvince() + "-" + ipInfo.getCity();
        }
        if (!Objects.equals(staff.getCompanyId(), staffHistory.getCompanyId())) {
            McOrganize companyHis = organizeService.findByKey(staffHistory.getOrgId());
            McOrganize companyNow = organizeService.findByKey(staff.getOrgId());
            // 编辑-公司
            StaffChangeLog staffChangeLog = new StaffChangeLog();
            staffChangeLog.setStaffId(staff.getStaffId());
            staffChangeLog.setBeforContent(companyHis == null || StringUtil.isBlank(companyHis.getOrgName()) ? "-" : companyHis.getOrgName());
            staffChangeLog.setAfterContent(companyNow == null || StringUtil.isBlank(companyNow.getOrgName()) ? "-" : companyNow.getOrgName());
            staffChangeLog.setChangeType(StaffChangeLogConsts.CHANGE_TYPE_EDIT_COMPANY);
            staffChangeLog.setIp(ip);
            staffChangeLog.setAbutmenterCity(abutmenterCity);
            this.addStaffChangeLog(staffChangeLog, loginStaff);
        }

        if (!Objects.equals(staff.getDeptId(), staffHistory.getDeptId())) {
            McDept deptHis = deptService.findByKey(staffHistory.getDeptId());
            McDept deptNow = deptService.findByKey(staff.getDeptId());
            // 编辑-所属部门岗位
            StaffChangeLog staffChangeLog = new StaffChangeLog();
            staffChangeLog.setStaffId(staff.getStaffId());
            staffChangeLog.setBeforContent(deptHis == null || StringUtil.isBlank(deptHis.getDeptName()) ? "-" : deptHis.getDeptName());
            staffChangeLog.setAfterContent(deptNow == null || StringUtil.isBlank(deptNow.getDeptName()) ? "-" : deptNow.getDeptName());
            staffChangeLog.setChangeType(StaffChangeLogConsts.CHANGE_TYPE_EDIT_DEPT);
            staffChangeLog.setIp(ip);
            staffChangeLog.setAbutmenterCity(abutmenterCity);
            this.addStaffChangeLog(staffChangeLog, loginStaff);
        }

        if (!Objects.equals(staff.getPositionLevel(), staffHistory.getPositionLevel())) {
            // 编辑-职级
            StaffChangeLog staffChangeLog = new StaffChangeLog();
            staffChangeLog.setStaffId(staff.getStaffId());
            if (NumberUtil.isNullOrZero(staffHistory.getPositionLevel())) {
                staffChangeLog.setBeforContent("-");
            } else {
                staffChangeLog.setBeforContent(StaffDict.getLevelName(staffHistory.getPositionLevel()));
            }
            if (NumberUtil.isNullOrZero(staff.getPositionLevel())) {
                staffChangeLog.setBeforContent("-");
            } else {
                staffChangeLog.setAfterContent(StaffDict.getLevelName(staff.getPositionLevel()));
            }
            staffChangeLog.setChangeType(StaffChangeLogConsts.CHANGE_TYPE_EDIT_POSITION);
            staffChangeLog.setIp(ip);
            staffChangeLog.setAbutmenterCity(abutmenterCity);
            this.addStaffChangeLog(staffChangeLog, loginStaff);
        }

        if (!Objects.equals(staff.getBecomeFormalTime(), staffHistory.getBecomeFormalTime())) {
            // 编辑-转正时间
            StaffChangeLog staffChangeLog = new StaffChangeLog();
            staffChangeLog.setStaffId(staff.getStaffId());
            staffChangeLog.setBeforContent(DateUtil.toString(staffHistory.getBecomeFormalTime()));
            staffChangeLog.setAfterContent(DateUtil.toString(staff.getBecomeFormalTime()));
            staffChangeLog.setChangeType(StaffChangeLogConsts.CHANGE_TYPE_EDIT_BECOME_FORMAL_TIME);
            staffChangeLog.setIp(ip);
            staffChangeLog.setAbutmenterCity(abutmenterCity);
            this.addStaffChangeLog(staffChangeLog, loginStaff);
        }

        if (!Objects.equals(staff.getStaffStatus(), staffHistory.getStaffStatus())) {
            // 编辑-状态
            StaffChangeLog staffChangeLog = new StaffChangeLog();
            staffChangeLog.setStaffId(staff.getStaffId());
            staffChangeLog.setBeforContent(NumberUtil.isNullOrZero(staffHistory.getStaffStatus()) ? "-" :
                    StaffDict.getStatusName(staffHistory.getStaffStatus()));
            staffChangeLog.setAfterContent(NumberUtil.isNullOrZero(staff.getStaffStatus()) ? "-" :
                    StaffDict.getStatusName(staff.getStaffStatus()));
            staffChangeLog.setChangeType(StaffChangeLogConsts.CHANGE_TYPE_STAFF_STATUS);
            staffChangeLog.setIp(ip);
            staffChangeLog.setAbutmenterCity(abutmenterCity);
            this.addStaffChangeLog(staffChangeLog, loginStaff);
        }

        if (!Objects.equals(staff.getType(), staffHistory.getType())) {
            // 编辑-员工类型
            StaffChangeLog staffChangeLog = new StaffChangeLog();
            staffChangeLog.setStaffId(staff.getStaffId());
            staffChangeLog.setBeforContent(NumberUtil.isNullOrZero(staffHistory.getType()) ? "-" :
                    StaffDict.getRoleTypeName(staffHistory.getType()));
            staffChangeLog.setAfterContent(NumberUtil.isNullOrZero(staff.getType()) ? "-" :
                    StaffDict.getRoleTypeName(staff.getType()));
            staffChangeLog.setChangeType(StaffChangeLogConsts.CHANGE_TYPE_ROLE_TYPE);
            staffChangeLog.setIp(ip);
            staffChangeLog.setAbutmenterCity(abutmenterCity);
            this.addStaffChangeLog(staffChangeLog, loginStaff);
        }

        if (!Objects.equals(staff.getMobile(), staffHistory.getMobile())) {
            // 编辑-手机号码
            StaffChangeLog staffChangeLog = new StaffChangeLog();
            staffChangeLog.setStaffId(staff.getStaffId());
            staffChangeLog.setBeforContent(StringUtils.isEmpty(staffHistory.getMobile()) ? "-" : staffHistory.getMobile());
            staffChangeLog.setAfterContent(StringUtils.isEmpty(staff.getMobile()) ? "-" : staff.getMobile());
            staffChangeLog.setChangeType(StaffChangeLogConsts.CHANGE_TYPE_MOBILE);
            staffChangeLog.setIp(ip);
            staffChangeLog.setAbutmenterCity(abutmenterCity);
            this.addStaffChangeLog(staffChangeLog, loginStaff);
        }

        // 编辑工作时间
        if (!Objects.equals(staff.getStartWorkTime(), staffHistory.getStartWorkTime())
                || !Objects.equals(staff.getStopWorkTime(), staffHistory.getStopWorkTime())) {
            String before = String.format("%s - %s", Objects.isNull(staffHistory.getStartWorkTime()) ? "-" : staffHistory.getStartWorkTime(), Objects.isNull(staffHistory.getStopWorkTime()) ? "-" : staffHistory.getStopWorkTime());
            String after = String.format("%s - %s", Objects.isNull(staff.getStartWorkTime()) ? "-" : staff.getStartWorkTime(), Objects.isNull(staff.getStopWorkTime()) ? "-" : staff.getStopWorkTime());
            StaffChangeLog staffChangeLog = new StaffChangeLog();
            staffChangeLog.setStaffId(staff.getStaffId());
            staffChangeLog.setBeforContent(before);
            staffChangeLog.setAfterContent(after);
            staffChangeLog.setChangeType(StaffChangeLogConsts.CHANGE_TYPE_WORK_TIME);
            staffChangeLog.setIp(ip);
            staffChangeLog.setAbutmenterCity(abutmenterCity);
            this.addStaffChangeLog(staffChangeLog, loginStaff);
        }
    }

    private void addStaffChangeLog(StaffChangeLog staffChangeLog, LoginStaff loginStaff) {
        staffChangeLog.setAbutmenterId(loginStaff.getStaffId());
        staffChangeLog.setAbutmenter(loginStaff.getRealName());
        staffChangeLog.setAbutmenterDeptId(loginStaff.getDeptId() + "");
        staffChangeLog.setAbutmenterDeptName(loginStaff.getDeptName());
        staffChangeLog.setCreateTime(DateUtil.getNow());
        staffChangeLogService.insert(staffChangeLog);
    }

    private void handleStaffChangeLogNotLimitIPDate(Integer staffId, Date startTime, Date endTime, LoginStaff loginStaff, String ip) {
        StaffChangeLog staffChangeLog = buildStaffLog(staffId, startTime, endTime, loginStaff, ip);
        staffChangeLogService.insert(staffChangeLog);
    }

    private void batchHandleStaffChangeLogNotLimitIPDate(List<Integer> staffIds, Date startTime, Date endTime, LoginStaff loginStaff, String ip) {
        List<StaffChangeLog> logList = staffIds.stream().map(x -> buildStaffLog(x, startTime, endTime, loginStaff, ip)).collect(Collectors.toList());

        staffChangeLogService.batchInsert(logList);
    }

    private StaffChangeLog buildStaffLog(Integer staffId, Date startTime, Date endTime, LoginStaff loginStaff, String ip) {
        StaffChangeLog staffChangeLog = new StaffChangeLog();
        staffChangeLog.setStaffId(staffId);
        StringBuilder sb = new StringBuilder();
        String hisDateRange = staffCache.getOpenDate(staffId);
        String hisStartTime = "";
        String hisEndTime = "";
        if (StringUtil.isNotBlank(hisDateRange)) {
            List<Date> hisLoginTimeRang = parseDateRangeStr(hisDateRange);
            if (hisLoginTimeRang.size() > 0) {
                hisStartTime = DateUtil.toString(hisLoginTimeRang.get(0));
            }
            if (hisLoginTimeRang.size() > 1) {
                hisEndTime = DateUtil.toString(hisLoginTimeRang.get(1));
            }
        }

        if (StringUtil.isNotBlank(hisStartTime)) {
            sb.append(hisStartTime).append("到");
        }
        if (StringUtil.isNotBlank(hisEndTime)) {
            sb.append(hisEndTime);
        }
        staffChangeLog.setBeforContent(sb.toString());
        staffChangeLog.setAfterContent(DateUtil.toString(startTime) + "到" + DateUtil.toString(endTime));
        staffChangeLog.setAbutmenterId(loginStaff.getStaffId());
        staffChangeLog.setAbutmenter(loginStaff.getRealName());
        staffChangeLog.setAbutmenterDeptId(loginStaff.getDeptId() + "");
        staffChangeLog.setAbutmenterDeptName(loginStaff.getDeptName());
        staffChangeLog.setChangeType(StaffChangeLogConsts.CHANGE_TYPE_NOT_LIMIT_DATE);
        staffChangeLog.setCreateTime(DateUtil.getNow());
        staffChangeLog.setIp(ip);
        IpInfo ipInfo = staffIpInfoBService.locationByIP(ip);
        if (Objects.nonNull(ipInfo)) {
            staffChangeLog.setAbutmenterCity(ipInfo.getProvince() + "-" + ipInfo.getCity());
        }
        return staffChangeLog;
    }

    String calcLengthOfService(Date date) {
        if (date == null) {
            return "";
        }

        Calendar cal = Calendar.getInstance();
        cal.set(Calendar.HOUR_OF_DAY, 0);
        cal.set(Calendar.MINUTE, 0);
        cal.set(Calendar.SECOND, 0);
        cal.set(Calendar.MILLISECOND, 0);

        Calendar setCal = Calendar.getInstance();
        setCal.setTime(date);
        setCal.set(Calendar.HOUR_OF_DAY, 0);
        setCal.set(Calendar.MINUTE, 0);
        setCal.set(Calendar.SECOND, 0);
        setCal.set(Calendar.MILLISECOND, 0);
        long yearToMilliSecond = (long) 1000 * 60 * 60 * 24 * 365;
        long yearDiff = (cal.getTimeInMillis() - setCal.getTimeInMillis()) / (yearToMilliSecond);

        if (yearDiff <= 0) {
            return "不足一年";
        } else {
            return yearDiff + "年";
        }
    }

    /**
     * 开放日期转换成字符串
     *
     * @param dateRange
     * @return
     */
    private List<Date> parseDateRangeStr(String dateRange) {
        return staffCache.parseDateRange(dateRange);
    }

    /**
     * 描述: 给新员工发送入职成功短信
     *
     * @param mobile 手机号码
     * @return
     * @author guoqiao
     * @since 2021/3/4 14:20
     */
    private void sendMessage(String mobile, String staffName, String password) {

        Map<String, Object> smsMap = new HashMap(16);
        smsMap.put("staffName", staffName);
        smsMap.put("password", password);
        SmsParams smsParams = SmsParams.builder()
                .mobile(mobile)
                .params(JSON.toJSONString(smsMap))
                .build();

        //封装send对象
        SendParams sendParams = SendParams.builder()
                .ruleId(ruleId)
                .callsName("MCC")
                .objectId("用户/工程师ID")
                .smsParams(smsParams)
                .build();
        String serialNumber = MessageClientIDSetter.createUniqID();
        //序列号
        sendParams.setSerialNumber(serialNumber);
        zmnMQSender.send(pushTopic, MQ_TAG, serialNumber, JSON.toJSONString(sendParams));
        logger.info("新员工发送短信成功,账号：{},密码:{}", staffName, password);
    }


    /**
     * 描述: 修改员工消息
     *
     * @param staff 员工信息
     * @author fengxuecai
     * @since 2021/5/17 16:28
     */
    private void sendStaffMessage(McStaff staff) {
        try {
            StaffEditMessageDRO staffEditMessage = new StaffEditMessageDRO();
            staffEditMessage.setStaffId(staff.getStaffId());
            staffEditMessage.setRealName(staff.getRealName());
            staffEditMessage.setDeptId(staff.getDeptId());
            staffEditMessage.setDeptName(staff.getDeptName());
            staffEditMessage.setUpdater(staff.getUpdater());
            McDept dept = deptService.findByKey(staff.getDeptId());
            if (Objects.nonNull(dept)) {
                staffEditMessage.setDeptLevel(dept.getLevel());
                staffEditMessage.setParentDeptId(dept.getParentId());
                McDept parentDept = deptService.findByKey(dept.getParentId());
                if (Objects.nonNull(parentDept)) {
                    staffEditMessage.setParentDeptName(parentDept.getDeptName());
                }
            }
            logger.info("员工修改发送mq消息,key:{},", SYNC_EDIT_STAFF_KEY);
            zmnMQSender.send(mccTopic, StaffConsts.SYNC_EDIT_STAFF_TAG, SYNC_EDIT_STAFF_KEY, JSON.toJSONString(staffEditMessage));
        } catch (Exception e) {
            logger.error("员工修改发送mq消息，msg:{}", e.getMessage());
        }
    }

    @Override
    public String getDTalkIdByMail(String mail) {
        if (StringUtil.isMail(mail) && mail.endsWith("@zmn.cn")) {
            return staffService.getDTalkIdByMail(mail);
        }
        return null;
    }

    @Override
    public List<McStaff> listByQuery(McStaffQuery query) {
        List<McStaff> mcStaffs = staffService.listByQuery(query);
        if (CollectionUtil.isNullOrEmpty(mcStaffs)) {
            return mcStaffs;
        }

        List<Integer> staffIds = mcStaffs.stream().map(McStaff::getStaffId).collect(Collectors.toList());
        for (; ; ) {
            List<McStaff> list = staffService.listByLeaderIds(staffIds);
            if (CollectionUtil.isNullOrEmpty(list)) {
                break;
            }
            mcStaffs.addAll(list);
            staffIds = list.stream().map(McStaff::getStaffId).collect(Collectors.toList());
        }


        return mcStaffs;
    }

    @Override
    public void dealIpOpenDate(String message) {
        if (org.apache.commons.lang3.StringUtils.isEmpty(message)) {
            return;
        }
        log.info("不限IP日期mq参数:{}", message);
        IpOpenDateMqDTO ipOpenDateMqDTO = JSON.parseObject(message, IpOpenDateMqDTO.class);
        //流程审批结束并且通过才会往后面走
        if (ObjectUtils.isEmpty(ipOpenDateMqDTO) || !org.apache.commons.lang3.StringUtils.equals(AUDIT_PASS, ipOpenDateMqDTO.getResult())
                || org.apache.commons.lang3.StringUtils.isEmpty(ipOpenDateMqDTO.getProcessInstanceId())) {
            return;
        }
        String processInstanceId = ipOpenDateMqDTO.getProcessInstanceId();
        ResponseDTO<ProcessInstanceTopDRO> processInstance = dingTalkAuditService.getProcessInstance(processInstanceId);
        if (!processInstance.isSuccess()) {
            log.error("不限IP日期获取流程实例失败:{}", processInstance.getMessage());
            return;
        }
        // 获取表单数据
        ProcessInstanceTopDRO data = processInstance.getData();
        List<OapiProcessinstanceGetResponse.FormComponentValueVo> formComponentValues = data.getFormComponentValues();
        log.info("不限IP日期processInstance返回结果:{}", formComponentValues);
        if (CollectionUtils.isEmpty(formComponentValues)) {
            return;
        }
        String startTime = null;
        String endTime = null;
        Map<String, String> staffs = new HashMap<>(16);
        for (OapiProcessinstanceGetResponse.FormComponentValueVo formComponentValue : formComponentValues) {
            if (org.apache.commons.lang3.StringUtils.equals("[\"外网开始时间\",\"外网结束时间\"]", formComponentValue.getName())) {
                String timeValue = formComponentValue.getValue();
                JSONArray jsonArray = JSONObject.parseArray(timeValue);
                //获取起始时间
                startTime = (String) jsonArray.get(0);
                endTime = (String) jsonArray.get(1);
            }
            if (org.apache.commons.lang3.StringUtils.equals("表格", formComponentValue.getName())) {
                String formValue = formComponentValue.getValue();
                List<Map> formArray = JSONObject.parseArray(formValue, Map.class);
                //获取员工信息
                getStaff(staffs, formArray);
            }
        }
        log.info("不限IP日期staffs:{}", staffs);
        //设置不限ip时间
        if (org.apache.commons.lang3.StringUtils.isNotBlank(startTime) && org.apache.commons.lang3.StringUtils.isNotBlank(endTime) && MapUtils.isNotEmpty(staffs)) {
            String timeString = getTimeString(startTime, endTime);

            staffs.forEach((k, v) -> {
                List<McStaff> mcStaffs = staffService.listByMobile(k);
                if (CollectionUtils.isNotEmpty(mcStaffs)) {
                    McStaff mcStaff = mcStaffs.stream().filter(e -> org.apache.commons.lang3.StringUtils.equals(v, e.getRealName())).max(Comparator.comparing(McStaff::getCreateTime)).orElse(null);
                    if (!ObjectUtils.isEmpty(mcStaff)) {
                        Date endTimeDate = null;

                        List<Date> lastLoginTimeRang = staffCache.parseDateRange(timeString);
                        if (lastLoginTimeRang.size() > 1) {
                            endTimeDate = lastLoginTimeRang.get(1);
                        }

                        // 开始日期与结束日期的时长，单位：秒
                        Date now = new Date();
                        if (ObjectUtils.isEmpty(endTimeDate) || endTimeDate.getTime() < now.getTime()) {
                            return;
                        }

                        //设置不限ip时间
                        long timeSpan = DateUtil.getTimespan(now, endTimeDate, DateUtil.UNIT_SECOND);
                        staffCache.setOpenDate(mcStaff.getStaffId(), timeString, (int) timeSpan);
                        log.info("不限IP日期mq设置redis数据:{},{}", mcStaff.getStaffId(), timeString);
                    }
                }
            });
        }
    }

    /**
     * 获取时间字符串
     *
     * @param startTime 起始时间
     * @param endTime   结束时间
     * @return
     */
    private String getTimeString(String startTime, String endTime) {
        return startTime.replace('-', '/')
                + PlatEngineConsts.SPLITER_SPACE
                + "00:00:00"
                + PlatEngineConsts.SPLITER_SPACE
                + PlatEngineConsts.SPLITER_UNDERLINE2
                + PlatEngineConsts.SPLITER_SPACE
                + endTime.replace('-', '/')
                + PlatEngineConsts.SPLITER_SPACE
                + "23:59:59";
    }

    /**
     * 获取员工姓名与手机号
     *
     * @param staffs    staffs
     * @param formArray formArray
     */
    @SuppressWarnings("rawtypes")
    private void getStaff(Map<String, String> staffs, List<Map> formArray) {
        for (Map form : formArray) {
            List<Map> rowValueMap = (List<Map>) form.get("rowValue");

            String staffName = null;
            String staffMobile = null;
            Map<String, String> staff = new HashMap<>(1);
            for (Map map : rowValueMap) {

                if (org.apache.commons.lang3.StringUtils.equals("姓名", String.valueOf(map.get("label")))) {
                    staffName = String.valueOf(map.get("value"));
                }
                if (org.apache.commons.lang3.StringUtils.equals("电话", String.valueOf(map.get("label")))) {
                    staffMobile = String.valueOf(map.get("value"));
                }
            }
            if (org.apache.commons.lang3.StringUtils.isNotBlank(staffName) && org.apache.commons.lang3.StringUtils.isNotBlank(staffMobile)) {
                staffs.put(staffMobile, staffName);
            }
        }
    }

    @Override
    public EngineerBasicInfoDRO getEngineerByStaffId(Integer staffId) {
        ResponseDTO<ForeignEngineerBasicInfoDRO> engineer = engineerListRemoteService.getEngineerByStaffId(staffId);
        EngineerBasicInfoDRO dro = new EngineerBasicInfoDRO();
        if (engineer.isSuccess() && !ObjectUtils.isEmpty(engineer.getData())) {
            log.info("getEngineerByStaffId工程师:staffId:{},{}", staffId, engineer.getData());
            BeanUtils.copyProperties(engineer.getData(), dro);
        } else {
            ResponseDTO<SupervisorDetailDRO> responseDTO = engineerSupervisorListRemoteService.getByStaffId(staffId);
            log.info("getEngineerByStaffId主管:staffId:{},{}", staffId, responseDTO);
            if (responseDTO.isSuccess() && !ObjectUtils.isEmpty(responseDTO.getData())) {
                SupervisorDetailDRO supervisor = responseDTO.getData();
                dro.setEngineerId(supervisor.getSupervisorId());
                dro.setRealName(supervisor.getSupervisorName());
                dro.setEngineerRole(com.zmn.consts.GlobalConsts.YES);
                dro.setSubCompanyId(supervisor.getSubCompanyId());
                dro.setSpCompanyId(supervisor.getSpCompanyId());
                dro.setStaffId(staffId);
                dro.setPlat(supervisor.getPlat());
            }
            //初始化默认选中主管
            dro.setEngineerRole(com.zmn.consts.GlobalConsts.YES);
            dro.setStaffId(staffId);
        }
        return dro;
    }

    @Override
    public Boolean bindingStaffAndMaster(Integer engineerRole, @NotNull(message = "参数不能为null") Integer staffId, @NotNull(message = "参数不能为null") Integer engineerId, @NotNull(message = "操作人不能为null") String operator) {
        //绑定工程师
        if (Objects.equals(engineerRole, com.zmn.consts.GlobalConsts.NO)) {
            ResponseDTO<Boolean> responseDTO = engineerBasicInfoModifyRemoteService.bindingStaff(staffId, engineerId, operator);
            if (responseDTO.isSuccess()) {
                log.info("bindingStaffAndMaster工程师:staffId:{}-engineerId:{}-{}", staffId, engineerId, responseDTO.getData());
                return responseDTO.getData();
            } else {
                log.error("bindingStaffAndMaster工程师:staffId:{}-engineerId:{}-{}", staffId, engineerId, responseDTO.getMessage());
            }
        }
        //绑定主管
        if (Objects.equals(engineerRole, com.zmn.consts.GlobalConsts.YES)) {
            ResponseDTO<Boolean> responseDTO1 = engineerSupervisorModifyRemoteService.bindingStaff(staffId, engineerId, operator);
            if (responseDTO1.isSuccess()) {
                log.info("bindingStaffAndMaster主管:staffId:{}-engineerId:{}-{}", staffId, engineerId, responseDTO1.getData());
                return responseDTO1.getData();
            } else {
                log.error("bindingStaffAndMaster主管:staffId:{}-engineerId:{}-{}", staffId, engineerId, responseDTO1.getMessage());
            }
        }
        return Boolean.FALSE;
    }
}